// // Copyright (c) 2009 All Right Reserved // // vl // // 2009-01-01 // Contains ... using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.Contracts; using System.IO; using System.Linq; using JetBrains.Annotations; using LargoCommon.Music; namespace LargoCommon.Midi { /// /// Midi Tone Collection. /// [Serializable] public sealed class MidiTones : IMidiTones { #region Fields /// /// Musical metric. /// [NonSerialized] private MusicalMetric metric; /// /// Last Bar Number. /// private int lastBarNumber; /// /// Current Delta Time. /// private long currentStartTime; /// /// Current Note. /// private byte currentNoteNumber; /// /// The list. /// private List list; #endregion #region Constructors /// /// Initializes a new instance of the class. /// /// The given midi track. public MidiTones(IMidiTrack givenMidiTrack) { Contract.Requires(givenMidiTrack != null); this.Metric = givenMidiTrack.Metric; this.List = new List(); this.MidiTrack = givenMidiTrack; this.Name = givenMidiTrack.Name; this.BarDivision = givenMidiTrack.BarDivision; this.InstrumentNumber = givenMidiTrack.InstrumentNumber; this.Channel = (byte)givenMidiTrack.Channel; this.Octave = givenMidiTrack.Octave; this.BandType = givenMidiTrack.BandType; this.Tempo = givenMidiTrack.Tempo; this.IsRhythmical = givenMidiTrack.IsRhythmical; if (this.BarDivision != 0) { this.ReadMidiTones(givenMidiTrack); } } /// /// Initializes a new instance of the class. /// /// The given midi track. /// The given area. public MidiTones(IMidiTrack givenMidiTrack, MusicalSection givenArea) : this(givenMidiTrack) { var tones = (from tone in this.List where tone.BarNumberFrom >= givenArea.BarFrom && tone.BarNumberFrom <= givenArea.BarTo //// && tone.StartTime > 18432 && tone.StartTime < 20000 orderby tone.BarNumberFrom, tone.StartTime ////, tone.Duration select tone).ToList(); this.SetTones(tones); this.SetFirstBarNumber(givenArea.BarFrom); } /// /// Initializes a new instance of the class. /// /// The list of tones. [UsedImplicitly] public MidiTones(IList list) { Contract.Requires(list != null); this.Metric = new MusicalMetric(1, 0); this.List = list.ToList(); } /// /// Prevents a default instance of the class from being created. /// private MidiTones() { this.Metric = new MusicalMetric(1, 0); this.List = new List(); } /// /// Initializes a new instance of the MidiTones class. /// /// The prototype. /// Given list. private MidiTones(MidiTones prototype, IEnumerable givenList) { Contract.Requires(prototype != null); this.MidiTrack = prototype.MidiTrack; this.Name = prototype.Name; this.Number = prototype.Number; this.BarDivision = prototype.BarDivision; this.InstrumentNumber = prototype.InstrumentNumber; this.Channel = prototype.Channel; this.Octave = prototype.Octave; this.BandType = prototype.BandType; this.IsRhythmical = prototype.IsRhythmical; this.Tempo = prototype.Tempo; this.Metric = prototype.Metric; this.List = givenList.ToList(); } #endregion #region Public properties /// /// Gets or sets the metric. /// /// /// The metric. /// /// Metric is null. /// Metric cannot be set null.;value public MusicalMetric Metric { get { Contract.Ensures(Contract.Result() != null); if (this.metric == null) { throw new InvalidOperationException("Metric is null."); } return this.metric; } set => this.metric = value ?? throw new ArgumentException("Metric cannot be set null.", nameof(value)); } /// /// Gets the list. /// /// Property description. public IList List { get => this.list; private set => this.list = (List)value; } /// /// Gets name of the collection. /// /// Property description. public string Name { get; } /// /// Gets the FirstBarNumber (because of division to blocks). /// /// Property description. public int FirstBarNumber { get; private set; } /// /// Gets BarDivision. /// /// Property description. public int BarDivision { get; } /// /// Gets instrument. /// /// Property description. public MusicalOctave Octave { get; } /// /// Gets instrument. /// /// Property description. public MusicalBand BandType { get; } /// /// Gets a value indicating whether Is Rhythmical. /// /// /// Property description. /// public bool IsRhythmical { get; } /// Gets musical tempo. /// Property description. private int Tempo { get; } /// /// Gets BarDivision. /// /// Property description. private int Number { get; } /// /// Gets instrument. /// /// Property description. private byte InstrumentNumber { get; } /// /// Gets Channel. /// /// Property description. private byte Channel { get; } /// /// Gets Tones Having Duration. /// /// Property description. private MidiTones TonesHavingDuration { get { IList listTones = this.List.Where(t => (t.Duration > 0)).OrderBy(t => t.StartTime).ToList(); var c = new MidiTones(this, listTones); return c; } } /// Gets or sets Collection of MIDI event added to this track. /// Property description. private Collection OtherEventList { get; set; } /// /// Gets the MidiTrack. /// /// Property description. private IMidiTrack MidiTrack { get; } #endregion /// /// Checks the tones - because of problem with sounding over end of tone /// /// Returns value. public bool CheckTones() { //// var status = true; var tones = (from tone in this.List where tone.Loudness > 0 && tone.BarNumberTo - tone.BarNumberFrom > 10 orderby tone.BarNumberFrom, tone.StartTime select tone).ToList(); if (tones.Count <= 0) { return true; } //// status = false; //// var tone1 = list.FirstOrDefault(); ////201508 MessageBox.Show(string.Format("Tone too long! \n {0} ", tone1)); //// foreach (var tone in list) { MessageBox.Show(string.Format("Tone too long! \n {0} ", tone)); } return false; } /// /// Check Rests. /// public void CheckRests() { var tones = (from tone in this.List where tone.Loudness == 0 && tone.BarNumberTo - tone.BarNumberFrom > 10 orderby tone.BarNumberFrom, tone.StartTime select tone).ToList(); if (tones.Count <= 0) { } ////201508 foreach (var tone in list) { MessageBox.Show(string.Format("Rest too long! \n {0} ", tone)); } } /// /// Sets the tones. /// /// The given list. public void SetTones(IEnumerable givenList) { Contract.Requires(givenList != null); this.list.Clear(); this.list.AddRange(givenList); } /// /// Sets the first bar number. /// /// The bar number. public void SetFirstBarNumber(int barNumber) { this.FirstBarNumber = barNumber; this.list.ForEach(mtone => { checked { mtone.BarNumberFrom -= barNumber - 1; mtone.BarNumberTo -= barNumber - 1; } }); } #region MidiTones /// /// Completes all tones. /// /// The midi track. /// The tones. /// All tones. private static void CompleteAllTones(IMidiTrack midiTrack, MidiTones tones, MidiTones allTones) { Contract.Requires(tones != null); Contract.Requires(midiTrack != null); Contract.Requires(allTones != null); long lastTime = 0; tones.list.ForEach(rmt => { if (rmt.StartTime > lastTime) { var mt = new MidiTone(lastTime, 0, 0, rmt.InstrumentNumber, rmt.Channel) { Duration = rmt.StartTime - lastTime, BarNumberFrom = (int)Math.Floor((double)lastTime / midiTrack.BarDivision) + 1, BarNumberTo = (int)Math.Floor((double)rmt.StartTime / midiTrack.BarDivision) + 1 }; allTones.List.Add(mt); } lastTime = rmt.StartTime + rmt.Duration; }); } /// /// Read Midi Tones. /// /// Midi Track. [ContractVerification(false)] private void ReadMidiTones(IMidiTrack midiTrack) { //// cyclomatic complexity 10:11 Contract.Requires(midiTrack != null); if (midiTrack.BarDivision == 0) { throw new InvalidOperationException("Bar division is zero."); } var tones = new MidiTones(); this.lastBarNumber = -1; //// !!!!!! 2013/11 midiTrack.Events.RecomputeAbsoluteTimes(); //// this.eventList.SortByStartTime(); var allTones = new MidiTones(); this.OtherEventList = new Collection(); this.currentStartTime = 0; this.currentNoteNumber = 0; const bool fullLength = true; this.PrepareMidiTones(midiTrack, tones, allTones, fullLength); allTones.AddRange(tones); //// when note off events are missing allTones.CompleteDurations(fullLength, midiTrack.BarDivision); midiTrack.Events.RecomputeDeltaTimes(); //// Where(t => (t.Velocity>0 && t.Duration==0) tones = allTones.TonesHavingDuration; CompleteAllTones(midiTrack, tones, allTones); //// 2014/12 Time optimization allTones.list.RemoveAll(mtone => mtone.Duration <= 0); var orderedTones = allTones.List.OrderBy(t => t.StartTime); //// var midiTones = allTones.List.Where(t => (t.Duration > 0)).OrderBy(t => t.StartTime).ToList(); this.list.AddRange(orderedTones); } /// /// Prepares the midi tones. /// /// The midi track. /// The tones. /// All tones. /// If set to true [full length]. private void PrepareMidiTones(IMidiTrack midiTrack, MidiTones tones, MidiTones allTones, bool fullLength) { Contract.Requires(midiTrack != null); Contract.Requires(midiTrack.Events != null); Contract.Requires(tones != null); Contract.Requires(allTones != null); var instrument = midiTrack.InstrumentNumber; var channel = midiTrack.Channel; foreach (var ev in midiTrack.Events) { bool noteOn = false, noteOff = false; VoiceNoteOn eventOn = null; var eventClass = ev.GetType().ToString(); var eventType = Path.GetExtension(eventClass); switch (eventType) { case ".VoiceProgramChange": { var programChange = (VoiceProgramChange)ev; instrument = programChange.Number; channel = programChange.Channel; continue; } case ".VoiceNoteOn": { eventOn = (VoiceNoteOn)ev; noteOn = eventOn.Velocity > 0; noteOff = eventOn.Velocity == 0; this.currentStartTime = eventOn.StartTime; this.currentNoteNumber = eventOn.Note; channel = eventOn.Channel; break; } case ".VoiceNoteOff": { var levOff = (VoiceNoteOff)ev; noteOff = true; this.currentStartTime = levOff.StartTime; this.currentNoteNumber = levOff.Note; break; } default: { if (!ev.IsMetaEvent) { this.OtherEventList.Add(ev); } break; } } if (noteOn) { this.AddNoteOnToTones(midiTrack, tones, instrument, channel, eventOn); } // ReSharper disable once InvertIf if (noteOff) { var mt = this.CompleteCurrentNote(midiTrack, tones, fullLength); if (mt == null) { continue; } allTones.List.Add(mt); tones.List.Remove(mt); } } } /// /// Completes the current note. /// /// The midi track. /// The tones. /// If set to true [full length]. /// Returns value. private IMidiTone CompleteCurrentNote(IMidiTrack midiTrack, MidiTones tones, bool fullLength) { Contract.Requires(tones != null); Contract.Requires(midiTrack != null); var noteNumber = this.currentNoteNumber; var toff = (from t in tones.List where t.NoteNumber == noteNumber select t).ToList(); if (!toff.Any()) { return null; } var mt = toff.First(); if (mt == null) { return null; } mt.CompleteDuration(this.currentStartTime, fullLength, midiTrack.BarDivision); var lastReadTone = tones.List.LastOrDefault(); //// MidiTone lastCompleteTone = allTones.LastOrDefault(); if (lastReadTone != null) { mt.SoundThrough = mt.StartTime < lastReadTone.StartTime; } return mt; } /// /// Adds the note on to tones. /// /// The midi track. /// The tones. /// The instrument. /// The channel. /// The voice note-on. private void AddNoteOnToTones(IMidiTrack midiTrack, MidiTones tones, byte instrument, MidiChannel channel, VoiceNoteOn eventOn) { Contract.Requires(midiTrack != null); Contract.Requires(eventOn != null); Contract.Requires(tones != null); var mt = new MidiTone(this.currentStartTime, this.currentNoteNumber, eventOn.Velocity, instrument, channel) { BarNumberFrom = (int)Math.Floor((double)this.currentStartTime / midiTrack.BarDivision) + 1 }; if (mt.BarNumberFrom != this.lastBarNumber) { this.lastBarNumber = mt.BarNumberFrom; mt.FirstInBar = true; } tones.List.Add(mt); } #endregion /// /// Complete Durations. /// /// Full length. /// Given division. private void CompleteDurations(bool fullLength, int givenDivision) { //// when note off events are missing for (var i = 0; i < this.List.Count - 1; i++) { var mt0 = this.List[i]; if (mt0 == null || mt0.Duration != 0) { continue; } var mt1 = this.List[i + 1]; if (mt1 != null) { mt0.CompleteDuration(mt1.StartTime, fullLength, givenDivision); } } } /// /// Add Collection. /// /// Given tones. private void AddRange(MidiTones givenTones) { Contract.Requires(givenTones != null); //// if (givenTone == null) { return false; } this.list.AddRange(givenTones.List); } } }